#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <netpacket/packet.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <netinet/in.h>
#include "eloop.h"
#include "cap.h"
#include "util.h"
#include "types.h"

#ifndef MSG_TRUNC
/*
 * This is being compiled on a system that lacks MSG_TRUNC; define it
 * with the value it has in the 2.2 and later kernels, so that, on
 * those kernels, when we pass it in the flags argument to "recvfrom()"
 * we're passing the right value and thus get the MSG_TRUNC behavior
 * we want.  (We don't get that behavior on 2.0[.x] kernels, because
 * they didn't support MSG_TRUNC.)
 */
#define MSG_TRUNC	0x20
#endif

/* max length of one packet, according to NLMSG_GOODSIZE in netlink.h
 * of kernel. */
#define MAX_PKT_LEN 8192
/* all raw packets recved stored in a buf one after another immediately */
#define DEF_ALL_PKT_BUF_SZ (2048 * 2048)

struct cap_des {
	char *ifname;
	char *savefile;
	int sock;
	FILE *fp;
	char *buf;	/* buf for all packets recved */
	int bufsz;
	char *pos;	/* packet position in buf */
	int pkt_cnt;	/* # packets recved */
};

static struct cap_des cdes = {
	.sock = -1,
	.bufsz = DEF_ALL_PKT_BUF_SZ,
};

/*
 * The first record in the file contains saved values for some
 * of the flags used in the printout phases of tcpdump.
 * Many fields here are 32 bit ints so compilers won't insert unwanted
 * padding; these files need to be interchangeable across architectures.
 *
 * Do not change the layout of this structure, in any way (this includes
 * changes that only affect the length of fields in this structure).
 *
 * Also, do not change the interpretation of any of the members of this
 * structure, in any way (this includes using values other than
 * LINKTYPE_ values, as defined in "savefile.c", in the "linktype"
 * field).
 *
 * Instead:
 *
 *	introduce a new structure for the new format, if the layout
 *	of the structure changed;
 *
 *	send mail to "tcpdump-workers@lists.tcpdump.org", requesting
 *	a new magic number for your new capture file format, and, when
 *	you get the new magic number, put it in "savefile.c";
 *
 *	use that magic number for save files with the changed file
 *	header;
 *
 *	make the code in "savefile.c" capable of reading files with
 *	the old file header as well as files with the new file header
 *	(using the magic number to determine the header format).
 *
 * Then supply the changes by forking the branch at
 *
 *	https://github.com/the-tcpdump-group/libpcap/issues
 *
 * and issuing a pull request, so that future versions of libpcap and
 * programs that use it (such as tcpdump) will be able to read your new
 * capture file format.
 */
struct pcap_file_header {
	u32 magic;
	u16 version_major;
	u16 version_minor;
	u32 thiszone;	/* gmt to local correction */
	u32 sigfigs;	/* accuracy of timestamps */
	u32 snaplen;	/* max length saved portion of each pkt */
	u32 linktype;	/* data link type (LINKTYPE_*) */
};

/*
 * This is a timeval as stored in a savefile.
 * It has to use the same types everywhere, independent of the actual
 * `struct timeval'; `struct timeval' has 32-bit tv_sec values on some
 * platforms and 64-bit tv_sec values on other platforms, and writing
 * out native `struct timeval' values would mean files could only be
 * read on systems with the same tv_sec size as the system on which
 * the file was written.
 */
struct pcap_timeval {
    u32 tv_sec;		/* seconds */
    u32 tv_usec;		/* microseconds */
};

/*
 * This is a 'pcap_pkthdr' as actually stored in a savefile.
 *
 * Do not change the format of this structure, in any way (this includes
 * changes that only affect the length of fields in this structure),
 * and do not make the time stamp anything other than seconds and
 * microseconds (e.g., seconds and nanoseconds).  Instead:
 *
 *	introduce a new structure for the new format;
 *
 *	send mail to "tcpdump-workers@lists.tcpdump.org", requesting
 *	a new magic number for your new capture file format, and, when
 *	you get the new magic number, put it in "savefile.c";
 *
 *	use that magic number for save files with the changed record
 *	header;
 *
 *	make the code in "savefile.c" capable of reading files with
 *	the old record header as well as files with the new record header
 *	(using the magic number to determine the header format).
 *
 * Then supply the changes by forking the branch at
 *
 *	https://github.com/the-tcpdump-group/libpcap/issues
 *
 * and issuing a pull request, so that future versions of libpcap and
 * programs that use it (such as tcpdump) will be able to read your new
 * capture file format.
 */
struct pcap_pkthdr {
    struct pcap_timeval ts;	/* time stamp */
    u32 caplen;		/* length of portion present */
    u32 len;		/* length this packet (off wire) */
};

#define PCAP_PKTHDR_SZ (sizeof(struct pcap_pkthdr))

/* 802.11 plus radiotap radio metadata header */
#define LINKTYPE_IEEE802_11_RADIOTAP 127


int cap_set_ifname(const char *ifname)
{
	cdes.ifname = strdup(ifname);
	if (!cdes.ifname) {
		perror("strdup");
		return -1;
	}

	return 0;
}

int cap_set_savefile(const char *savefile)
{
	cdes.savefile = strdup(savefile);
	if (!cdes.savefile) {
		perror("strdup");
		return -1;
	}

	return 0;
}

void cap_set_bufsz(int sz)
{
	cdes.bufsz = sz * 1024;
}

static int open_sock(const char *ifname)
{
	struct ifreq ifr;
	struct sockaddr_ll ll;
	int protocol = ETH_P_ALL;
	int fd;

	fd = socket(PF_PACKET, SOCK_RAW, htons(protocol));
	if (fd < 0) {
		printf("%s: socket(PF_PACKET): %s\n",
				__func__, strerror(errno));
		return -1;
	}

	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
	if (ioctl(fd, SIOCGIFINDEX, &ifr) < 0) {
		printf("%s: ioctl[SIOCGIFINDEX]: %s\n",
				__func__, strerror(errno));
		close(fd);
		return -1;
	}

	memset(&ll, 0, sizeof(ll));
	ll.sll_family = PF_PACKET;
	ll.sll_ifindex = ifr.ifr_ifindex;
	ll.sll_protocol = htons(protocol);
	if (bind(fd, (struct sockaddr *) &ll, sizeof(ll)) < 0) {
		printf("%s: bind[PF_PACKET]: %s\n",
				__func__, strerror(errno));
		close(fd);
		return -1;
	}

	if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) {
		printf("%s: ioctl[SIOCGIFHWADDR]: %s\n",
				__func__, strerror(errno));
		close(fd);
		return -1;
	}
	printf("%s address: ", ifname);
	print_mac_address(ifr.ifr_hwaddr.sa_data);

	return fd;
}

static int pcap_save_file_header(int snaplen)
{
	struct pcap_file_header hdr;
	size_t sz = sizeof(hdr);
	int ret;

	hdr.magic = 0xa1b2c3d4;
	hdr.version_major = 2;
	hdr.version_minor = 4;

	hdr.thiszone = 0;
	hdr.sigfigs = 0;
	hdr.snaplen = snaplen;
	hdr.linktype = LINKTYPE_IEEE802_11_RADIOTAP;

	rewind(cdes.fp);
	ret = fwrite((void *)&hdr, sz, 1, cdes.fp);
	if (ret != 1) {
		perror("fwrite");
		return -1;
	}

	printf("%lu bytes pcap header written to %s\n",
		(unsigned long)sz, cdes.savefile);
	return (int)sz;
}

static int pcap_write_packet_header(char *buf, int snaplen)
{
	struct pcap_pkthdr *hdr;
	struct timeval ts;
	int ret;
	
	hdr = (struct pcap_pkthdr *)buf;
	ret = gettimeofday(&ts, NULL);
	if (ret) {
		perror("gettimeofday");
		return -1;
	}

	hdr->ts.tv_sec = ts.tv_sec;
	hdr->ts.tv_usec = ts.tv_usec;
	hdr->caplen = snaplen;
	hdr->len = snaplen;

	return sizeof(*hdr);
}

static int pcap_save_all_packets(void)
{
	size_t ret;
	size_t n;
	FILE *fp = cdes.fp;
	char *buf = cdes.buf;

	if (!fp || !buf)
		return -1;
	n = cdes.pos - buf;

	/* no snap with max 65535 */
	ret = pcap_save_file_header(0xffff);

	ret = fwrite((void *)buf, n, 1, fp);
	if (ret != 1) {
		perror("fwrite");
		return -1;
	}

	fflush(fp);
	fsync(fileno(fp));
	printf("%lu bytes %d packets written to %s\n",
		(unsigned long)n, cdes.pkt_cnt, cdes.savefile);

	return 0;
}

static int __cap_packet(int fd, char *buf)
{
	int ret;
	ssize_t len;
	fd_set rfds;
	struct timeval tv;
	static int if_down = 0;

	FD_ZERO(&rfds);
	FD_SET(fd, &rfds);

	/* wait one second */
	tv.tv_sec = 1;
	tv.tv_usec = 0;

	ret = select(fd + 1, &rfds, NULL, NULL, &tv);
	if (ret < 0) {
		perror("select");
		return -1;
	} else if (0 == ret) {
		if (if_down)
			return 0;
	}

	if (!FD_ISSET(fd, &rfds))
		return 0;

	len = recv(fd, buf, MAX_PKT_LEN, MSG_TRUNC);
	if (-1 == len) {
		perror("recv");
		switch (errno) {
		case EINTR:	/* ignore EINTR */
		case EAGAIN:	/* no packet there */
			return 0;
		case ENETDOWN:
			/*
			 * The device on which we're capturing went away
			 * or not up at this moment.
			 */
			if_down = 1;
		default:
			return -1;
		}
	}

	if_down = 0;

	/* the real length of the packet longer than the passed buffer */
	if (len > MAX_PKT_LEN) {
		printf("packet too long, len: %d\n", (int)len);
		len = MAX_PKT_LEN;
	}

	return (int)len;
}

int cap_packet(void)
{
	int ret = -1;
	char *pos = cdes.pos;
	char *next = pos + PCAP_PKTHDR_SZ + MAX_PKT_LEN;
	time_t now;
	static time_t last = 0;

	//printf("pos: %p\n", pos);
	if ((long)(next - cdes.buf) > cdes.bufsz) {
		printf("packet buffer nearly full!\n");
		eloop_break();
		return ret;
	}

	ret = __cap_packet(cdes.sock, pos + PCAP_PKTHDR_SZ);
	if (ret <= 0)
		return ret;

	//printf("%d bytes\n", ret);
	pcap_write_packet_header(pos, ret);
	cdes.pos += ret + PCAP_PKTHDR_SZ;
	cdes.pkt_cnt++;

	now = time(NULL);
	if (((long)now - last) >= 3) {
		last = now;
		printf("packets received: %d\n", cdes.pkt_cnt);
	}

	return ret;
}

int cap_open(void)
{
	int ret;
	FILE *fp;

	if (!cdes.ifname)
		cdes.ifname = "wlan0";
	if (!cdes.savefile)
		cdes.savefile = "./tmp.pcap";
	printf("ifname: %s\nsavefile: %s\n",
		cdes.ifname, cdes.savefile);

	ret = open_sock(cdes.ifname);
	if (ret < 0)
		return -1;
	cdes.sock = ret;

	fp = fopen(cdes.savefile, "w");
	if (fp == NULL) {
		perror("fopen");
		cap_close();
		return -1;
	}
	cdes.fp = fp;

	cdes.buf = (char *)malloc(cdes.bufsz);
	if (!cdes.buf) {
		perror("malloc");
		cap_close();
		return -1;
	}
	cdes.pos = cdes.buf;

	printf("buf: %p, size: %d\n", cdes.buf, cdes.bufsz);
	return 0;
}

void cap_close(void)
{
	if (cdes.sock >= 0)
		close(cdes.sock);
	if (cdes.pkt_cnt > 0)
		pcap_save_all_packets();
	if (cdes.fp != NULL)
		fclose(cdes.fp);
	if (cdes.buf != NULL)
		free(cdes.buf);
}
